package dao

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"time"

	v1 "open/api/open/service/v1"
	"open/internal/model/event"
	"open/internal/types"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-redis/redis/v8"
	"github.com/qiniu/go-sdk/v7/auth/qbox"
	"github.com/qiniu/go-sdk/v7/storage"
	"github.com/tencentyun/cos-go-sdk-v5"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	wechat "gitee.com/quantum-force/wxopen-api"
	admin "open/api/open/admin/v1"
	"open/internal/model"
	"open/internal/model/vo"
)

const wechatComponentAccessTokenKey = "open/wechat/component_access_token/%s"
const wechatAccessTokenKey = "open/wechat/access_token/%s"
const wechatAuthorizerAccessTokenKey = "open/wechat/authorizer_access_token/%s"
const wechatJsTicketKey = "open/wechat/js_ticket/%s"
const wechatUserSessionKey = "open/wechat/session_key/%s/%s"

// GetWechatComponentAccessToken 获取componentAccessToken
func (d *Dao) GetWechatComponentAccessToken(ctx context.Context) (appID, token string, err error) {
	appID = d.conf.Open.Wechat.AppId
	key := fmt.Sprintf(wechatComponentAccessTokenKey, appID)
	token, err = d.cache.Get(ctx, key).Result()
	if err != nil {
		if err == redis.Nil {
			// 从微信获取Token
			openApp := new(model.WechatOpenApp)
			err := d.FromContext(ctx).Where("app_id = ?", appID).First(openApp).Error
			if err != nil {
				return "", "", err
			}
			resp, _, err := d.wechat.Component.APIComponentToken(ctx, &wechat.APIComponentTokenRequest{
				ComponentAppID:        appID,
				ComponentAppSecret:    openApp.AppSecret,
				ComponentVerifyTicket: openApp.Ticket,
			})
			if err != nil {
				return "", "", err
			}
			if err = d.cache.Set(ctx, key, resp.ComponentAccessToken, time.Second*time.Duration(resp.ExpiresIn-100)).Err(); err != nil {
				return "", "", err
			}
			return appID, resp.ComponentAccessToken, nil
		} else {
			return "", "", err
		}
	}

	return appID, token, err
}

// GetWechatAuthorizerAccessToken 获取授权者token
func (d *Dao) GetWechatAuthorizerAccessToken(ctx context.Context, subAppID string) (token string, err error) {
	tokenKey := fmt.Sprintf(wechatAuthorizerAccessTokenKey, subAppID)
	token, err = d.cache.Get(ctx, tokenKey).Result()
	if err != nil {
		if err == redis.Nil {
			app := new(model.WechatOpenSubApp)
			err = d.db.WithContext(ctx).Where("app_id = ? AND status = ?", subAppID, types.WechatAuthorized).First(app).Error
			if err != nil {
				if errors.Is(err, gorm.ErrRecordNotFound) {
					err = errors.Unauthorized(
						admin.ErrorCode_AUTH_UNAUTHORIZED.String(),
						"应用未授权或者商家主动取消授权,请重新授权后重试！")
				}
				return "", err
			}
			if app.ExpiredAt != nil {
				if app.ExpiredAt.After(time.Now()) {
					if err = d.cache.SetEX(ctx, tokenKey, app.Token, time.Until(*app.ExpiredAt)).Err(); err != nil {
						return "", err
					}
					return app.Token, nil
				}
			}
			componentAppID, componentToken, err := d.GetWechatComponentAccessToken(ctx)
			if err != nil {
				return "", fmt.Errorf("获取component_access_token失败: %d", err)
			}
			resp, err := d.wechat.Component.GetAuthorizerToken(ctx, componentToken, &wechat.GetAuthorizerTokenRequest{
				ComponentAppID:         componentAppID,
				AuthorizerAppID:        app.AppID,
				AuthorizerRefreshToken: app.RefreshToken,
			})
			if err != nil {
				return "", err
			}
			// 设置token
			if err = d.cache.Set(ctx, tokenKey, resp.AuthorizerAccessToken, 7100*time.Second).Err(); err != nil {
				return "", err
			}
			// 落库
			app.Token = resp.AuthorizerAccessToken
			app.RefreshToken = resp.AuthorizerRefreshToken
			ExpiredAt := time.Now().Add(time.Second * time.Duration(resp.ExpiresIn-100))
			app.ExpiredAt = &ExpiredAt
			if err = d.db.Save(app).Error; err != nil {
				return "", err
			}
			return resp.AuthorizerAccessToken, nil
		} else {
			return "", err
		}
	}
	return token, nil
}

// GetWechatPreAuthCode 创建预授权码
func (d *Dao) GetWechatPreAuthCode(ctx context.Context) (*admin.GetWechatPreAuthCodeReply, error) {
	componentAppID, componentAccessToken, err := d.GetWechatComponentAccessToken(ctx)
	if err != nil {
		return nil, err
	}
	resp, err := d.wechat.Component.CreatePreAuthCode(ctx, componentAccessToken,
		&wechat.CreatePreAuthCodeRequest{
			ComponentAppID: componentAppID,
		})
	if err != nil {
		return nil, err
	}
	return &admin.GetWechatPreAuthCodeReply{
		PreAuthCode:    resp.PreAuthCode,
		ExpiresIn:      resp.ExpiresIn,
		ComponentAppId: componentAppID,
	}, nil
}

// GetWechatOpenApp 获取微信开放平台应用
func (d *Dao) GetWechatOpenApp(ctx context.Context, appID string) (app *model.WechatOpenApp, err error) {
	app = new(model.WechatOpenApp)
	err = d.FromContext(ctx).Where("app_id = ?", appID).First(app).Error
	return
}

// SendWechatCustomMessage 发送微信自定义客服消息
func (d *Dao) SendWechatCustomMessage(ctx context.Context, token string, req *v1.SendWechatCustomMessageRequest) error {
	msgReq := &wechat.SendCustomMessageRequest{
		ToUser:          req.ToUser,
		MsgType:         req.MsgType,
		WxCard:          nil,
		MiniProgramPage: nil,
	}
	if req.Text != nil {
		msgReq.Text = &wechat.Text{Content: req.Text.Content}
	}
	if req.Image != nil {
		msgReq.Image = &wechat.Image{MediaID: req.Image.MediaId}
	}
	if req.Voice != nil {
		msgReq.Voice = &wechat.Voice{MediaID: req.Voice.MediaId}
	}
	if req.Video != nil {
		msgReq.Video = &wechat.Video{
			MediaID:      req.Video.MediaId,
			ThumbMediaID: req.Video.ThumbMediaId,
			Title:        req.Video.Title,
			Description:  req.Video.Description,
		}
	}
	if req.Music != nil {
		msgReq.Music = &wechat.Music{
			Title:        req.Music.Title,
			Description:  req.Music.Description,
			MusicURL:     req.Music.MusicUrl,
			HQMusicURL:   req.Music.HqMusicUrl,
			ThumbMediaID: req.Music.ThumbMediaId,
		}
	}
	if req.News != nil {
		msgReq.News = &wechat.News{Articles: []*wechat.Article{
			{Title: req.News.Title,
				Description: req.News.Description,
				URL:         req.News.Url,
				PicURL:      req.News.PicUrl,
			},
		},
		}
	}
	if req.MpNews != nil {
		msgReq.MPNews = &wechat.MPNews{MediaID: req.MpNews.MediaId}
	}
	if req.MsgMenu != nil {
		var list []*wechat.MenuContent
		if req.MsgMenu.MenuList != nil {
			list = make([]*wechat.MenuContent, len(req.MsgMenu.MenuList))
			for i, m := range req.MsgMenu.MenuList {
				list[i] = &wechat.MenuContent{
					ID:      m.Id,
					Content: m.Content,
				}
			}
		}
		msgReq.MsgMenu = &wechat.MsgMenu{
			HeadContent: req.MsgMenu.HeadContent,
			MenuList:    list,
			TailContent: req.MsgMenu.TailContent,
		}
	}

	if req.WxCard != nil {
		msgReq.WxCard = &wechat.WXCard{CardID: req.WxCard.CardId}
	}
	if req.MiniProgramPage != nil {
		msgReq.MiniProgramPage = &wechat.MiniProgramPage{
			Title:        req.MiniProgramPage.Title,
			AppID:        req.MiniProgramPage.AppId,
			PagePath:     req.MiniProgramPage.PagePath,
			ThumbMediaID: req.MiniProgramPage.ThumbMediaId,
		}
	}
	return d.wechat.MP.SendCustomMessage(ctx, token, msgReq)
}

// GetWechatMPAccessToken 获取微信公众号请求token
func (d *Dao) GetWechatMPAccessToken(ctx context.Context, appID string) (token string, err error) {
	key := fmt.Sprintf(wechatAccessTokenKey, appID)
	token, err = d.cache.Get(ctx, key).Result()
	if err != nil {
		if err == redis.Nil {
			app := new(model.WechatMPApp)
			err = d.db.WithContext(ctx).Where("app_id = ?", appID).First(app).Error
			if err != nil {
				return "", err
			}
			resp, _, err := d.wechat.MP.GetAccessToken(ctx, app.AppID, app.AppSecret)
			if err != nil {
				return "", err
			}
			if err = d.cache.Set(ctx, key, resp.AccessToken, time.Second*time.Duration(resp.ExpiresIn-100)).Err(); err != nil {
				return "", err
			}
			return resp.AccessToken, nil
		}
	}
	return token, nil
}

// GetWechatUserInfo 获取微信用户信息
func (d *Dao) GetWechatUserInfo(ctx context.Context, token string, openID string) (user *vo.WechatUserInfo, err error) {
	resp, err := d.wechat.MP.GetUserInfo(ctx, token, openID, "zh_CN")
	if err != nil {
		return nil, err
	}
	return &vo.WechatUserInfo{
		Subscribe: resp.Subscribe,
		OpenID:    resp.OpenID,
		UnionID:   resp.UnionID,
		Nickname:  resp.Nickname,
		Avatar:    resp.Avatar,
		Sex:       resp.Sex,
		City:      resp.City,
		Country:   resp.Country,
		Province:  resp.Province,
		Language:  resp.Language,
		QRScene:   resp.QrScene,
	}, nil
}

// GetWechatMPApp 获取微信公众号
func (d *Dao) GetWechatMPApp(ctx context.Context, appID string) (app *model.WechatMPApp, err error) {
	app = new(model.WechatMPApp)
	err = d.FromContext(ctx).Where("app_id = ?", appID).First(app).Error
	return
}

// AuthorizeWechat 微信授权
func (d *Dao) AuthorizeWechat(ctx context.Context, authorizationCode string) (*vo.AuthInfo, error) {
	componentAppID, componentAccessToken, err := d.GetWechatComponentAccessToken(ctx)
	if err != nil {
		return nil, err
	}
	resp, err := d.wechat.Component.AuthApp(ctx, componentAccessToken, &wechat.AuthAppRequest{
		ComponentAppID:    componentAppID,
		AuthorizationCode: authorizationCode,
	})
	if err != nil {
		return nil, err
	}
	return &vo.AuthInfo{
		Token:        resp.AuthorizationInfo.AuthorizerAccessToken,
		AppID:        resp.AuthorizationInfo.AuthorizerAppID,
		RefreshToken: resp.AuthorizationInfo.AuthorizerRefreshToken,
		ExpiredAt:    time.Now().Add(time.Duration(resp.AuthorizationInfo.ExpiresIn-100) * time.Second),
		OpenAppID:    componentAppID,
	}, nil
}

// GetWechatSubApp 获取微信开放平台子应用
func (d *Dao) GetWechatSubApp(ctx context.Context, appID string) (app *model.WechatOpenSubApp, err error) {
	app = new(model.WechatOpenSubApp)
	err = d.FromContext(ctx).Where("app_id = ?", appID).First(app).Error
	return
}

// LockWechatAuthApp 锁定微信子应用
func (d *Dao) LockWechatAuthApp(ctx context.Context, appID string) (app *model.WechatOpenSubApp, err error) {
	app = &model.WechatOpenSubApp{}
	err = d.FromContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_id = ?", appID).First(app).Error
	return
}

// CreateWechatSubApp 创建微信开放平台子应用
func (d *Dao) CreateWechatSubApp(ctx context.Context, app *model.WechatOpenSubApp) error {
	return d.FromContext(ctx).Create(app).Error
}

// SaveWechatSubApp 保存微信开放平台子应用
func (d *Dao) SaveWechatSubApp(ctx context.Context, app *model.WechatOpenSubApp) error {
	err := d.FromContext(ctx).Save(app).Error
	if err != nil {
		return err
	}
	return d.cache.Del(ctx, fmt.Sprintf(wechatAuthorizerAccessTokenKey, app.AppID)).Err()
}

// SaveWechatOpenApp 保存微信开放平台应用
func (d *Dao) SaveWechatOpenApp(ctx context.Context, app *model.WechatOpenApp) error {
	return d.FromContext(ctx).Save(app).Error
}

// GetWechatSession 微信小程序登录
func (d *Dao) GetWechatSession(ctx context.Context, componentAppID, componentToken string, appID string, code string) (*vo.WXALoginInfo, error) {
	resp, err := d.wechat.Component.JsCode2Session(ctx, componentToken, &wechat.JsCode2SessionRequest{
		AppID:          appID,
		JSCode:         code,
		ComponentAppID: componentAppID,
	})
	if err != nil {
		return nil, err
	}
	// 缓存session_key 用以解密用户加密数据
	if err := d.cache.Set(ctx, fmt.Sprintf(wechatUserSessionKey, appID, resp.OpenID), resp.SessionKey, time.Hour*24).Err(); err != nil {
		return nil, err
	}
	return &vo.WXALoginInfo{
		OpenID:     resp.OpenID,
		UnionID:    resp.UnionID,
		SessionKey: resp.SessionKey,
	}, nil
}

// GetWechatUserSessionKey 获取微信登录的sessionKey
func (d *Dao) GetWechatUserSessionKey(ctx context.Context, appID, openID string) (string, error) {
	sessionKey, err := d.cache.Get(ctx, fmt.Sprintf(wechatUserSessionKey, appID, openID)).Result()
	if err != nil {
		if err == redis.Nil {
			return "", nil
		}
		return "", err
	}
	return sessionKey, nil
}

// GetWXATesters 获取小程序体验者
func (d *Dao) GetWXATesters(ctx context.Context, token string) ([]string, error) {
	resp, _, err := d.wechat.WXA.MemberAuth(ctx, token)
	if err != nil {
		return nil, err
	}
	testers := make([]string, len(resp.Members))
	for i, m := range resp.Members {
		testers[i] = m.UserString
	}
	return testers, nil
}

// CreateWechatQRCodeTicket 创建带参数二维码
func (d *Dao) CreateWechatQRCodeTicket(ctx context.Context, token string, req *v1.CreateQRCodeTicketRequest) (*vo.QRCodeTicket, error) {
	resp, _, err := d.wechat.MP.CreateQRCodeTicket(ctx, token, &wechat.CreateQRCodeTicketRequest{
		ActionName: req.ActionName,
		ActionInfo: &wechat.ActionInfo{
			SceneID:  req.ActionInfo.SceneId,
			SceneStr: req.ActionInfo.SceneStr,
		},
		ExpireSeconds: req.ExpireSeconds,
	})
	if err != nil {
		return nil, err
	}
	return &vo.QRCodeTicket{
		Ticket:        resp.Ticket,
		ExpireSeconds: resp.ExpireSeconds,
		URL:           resp.URL,
	}, nil
}

// UpdateWechatQRCode 用COS保存更新的小程序二维码(默认)
func (d *Dao) UpdateWechatQRCode(ctx context.Context, appID string) error {
	app := new(model.WechatMiniApp)
	err := d.FromContext(ctx).Where("app_id = ?", appID).First(app).Error
	if err != nil {
		return err
	}
	if app.QrCodeURL != "" {
		return nil
	}
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return err
	}
	resp, err := d.wechat.WXA.GetWXACodeUnlimit(ctx, token, &wechat.GetWXACodeUnlimitRequest{Scene: "public"})
	if err != nil {
		return fmt.Errorf("获取微信小程序:%v(%v)二维码失败: %w", app.Name, app.AppID, err)
	}
	defer resp.Body.Close()
	key := fmt.Sprintf("wechat-mina-%s.jpg", appID)

	cosConf := d.conf.Cos

	u, _ := url.Parse(cosConf.ImgResourceBucketUri)
	b := &cos.BaseURL{
		BucketURL: u,
	}
	client := cos.NewClient(b, &http.Client{
		Transport: &cos.AuthorizationTransport{
			SecretID:  cosConf.SecretId,
			SecretKey: cosConf.SecretKey,
		},
	})
	fmt.Println("UpdateWechatQRCode cos uploader init ok")
	if _, err = client.Object.Put(ctx, key, resp.Body, nil); err != nil {
		return fmt.Errorf("UpdateWechatQRCode cos upload error :%s ", err.Error())
	}

	app.QrCodeURL = d.conf.Cos.ImgResourceUri + "/" + key
	err = d.FromContext(ctx).Save(app).Error
	if err != nil {
		return err
	}
	return nil
}

// UpdateWechatQRCodeByQiniu 用qiniu保存更新的小程序二维码(obsolete)
func (d *Dao) UpdateWechatQRCodeByQiniu(ctx context.Context, appID string) error {
	app := new(model.WechatMiniApp)
	err := d.FromContext(ctx).Where("app_id = ?", appID).First(app).Error
	if err != nil {
		return err
	}
	if app.QrCodeURL != "" {
		return nil
	}
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return err
	}
	resp, err := d.wechat.WXA.GetWXACodeUnlimit(ctx, token, &wechat.GetWXACodeUnlimitRequest{Scene: "public"})
	if err != nil {
		return fmt.Errorf("获取微信小程序:%v(%v)二维码失败: %w", app.Name, app.AppID, err)
	}
	defer resp.Body.Close()
	key := fmt.Sprintf("wechat-mina-%s.jpg", appID)
	putPolicy := storage.PutPolicy{
		Scope: fmt.Sprintf("image:%v", key),
	}
	mac := qbox.NewMac(d.conf.Qn.AccessKey, d.conf.Qn.SecretKey)
	upToken := putPolicy.UploadToken(mac)
	cfg := storage.Config{}
	cfg.Zone = &storage.ZoneHuadong
	cfg.UseHTTPS = true
	cfg.UseCdnDomains = false
	formUploader := storage.NewFormUploader(&cfg)
	ret := storage.PutRet{}
	buffer := make([]byte, resp.ContentLength)
	dataLen, _ := io.ReadFull(resp.Body, buffer)
	err = formUploader.Put(ctx, &ret, upToken, key, bytes.NewReader(buffer), int64(dataLen), nil)
	if err != nil {
		return fmt.Errorf("七牛上传失败")
	}
	app.QrCodeURL = d.conf.Qn.BucketUrl + "/" + ret.Key
	err = d.FromContext(ctx).Save(app).Error
	if err != nil {
		return err
	}
	return nil
}

// CreateWechatMiniApp 获取或创建微信小程序应用信息
func (d *Dao) CreateWechatMiniApp(ctx context.Context, app *model.WechatMiniApp) error {
	return d.FromContext(ctx).Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "app_id"}},
		UpdateAll: true,
	}).Create(app).Error
}

// FindWechatIDUserStringRefs 查询微信userString ref
func (d *Dao) FindWechatIDUserStringRefs(ctx context.Context, userStrings []string) (refs []*model.WechatUserString, err error) {
	err = d.FromContext(ctx).Where("user_str in ?", userStrings).Find(&refs).Error
	return
}

// GetWechatMiniTesters 获取微信小程序体验者
func (d *Dao) GetWechatMiniTesters(ctx context.Context, appID string) (testers []*wechat.Tester, err error) {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	var resp *wechat.Testers
	resp, _, err = d.wechat.WXA.MemberAuth(ctx, token)
	if err != nil {
		wechatErr, ok := err.(*wechat.ErrorResponse)
		if !ok {
			return
		}
		switch wechatErr.Code {
		case 40001:
			err = errors.BadRequest(
				admin.ErrorCode_AUTH_UNAUTHORIZED.String(), "授权失效或不是最新，请稍后重试")
		case 61007:
			err = errors.BadRequest(

				"NO_PERMISSION",
				"权限不足，请重新发起微信小程序授权，并勾选相关管理权限后重试",
			)
		}
		return
	}
	testers = resp.Members
	return
}

// AddWechatMiniTester 微信增加体验者
func (d *Dao) AddWechatMiniTester(ctx context.Context, appID string, wechatID string) (err error) {
	var token string
	token, err = d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return err
	}
	var tester *wechat.Tester
	if tester, _, err = d.wechat.WXA.BindTester(ctx, token, &wechat.BindTesterRequest{WechatID: wechatID}); err != nil {
		return
	}
	if err = d.FromContext(ctx).Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "user_str"}},
		UpdateAll: true,
	}).Create(&model.WechatUserString{
		UserStr:  tester.UserString,
		WechatID: wechatID,
	}).Error; err != nil {
		return
	}
	return
}

// DeleteWechatMiniTester 删除微信小程序体验者
func (d *Dao) DeleteWechatMiniTester(ctx context.Context, appID string, wechatID, userString string) error {
	wechatReq := &wechat.UnBindTesterRequest{
		UserString: userString,
		WechatID:   wechatID,
	}
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return err
	}
	if _, err := d.wechat.WXA.UnBindTester(ctx, token, wechatReq); err != nil {
		return err
	}
	return nil
}

// SaveWechatMiniApp 保存微信小程序应用信息
func (d *Dao) SaveWechatMiniApp(ctx context.Context, app *model.WechatMiniApp) error {
	return d.FromContext(ctx).Save(app).Error
}

// GetWechatMiniApp 获取微信小程序信息
func (d *Dao) GetWechatMiniApp(ctx context.Context, appID string) (app *model.WechatMiniApp, err error) {
	app = new(model.WechatMiniApp)
	err = d.FromContext(ctx).Where("app_id = ?", appID).First(app).Error
	return
}

// GetWechatAuthorizerInfo 获取微信授权者信息
func (d *Dao) GetWechatAuthorizerInfo(ctx context.Context, authorizerAppID string) (*wechat.AuthorizerInfo, error) {
	_, componentToken, err := d.GetWechatComponentAccessToken(ctx)
	if err != nil {
		return nil, err
	}
	info, err := d.wechat.Component.GetAuthorizerInfo(ctx, componentToken, &wechat.GetAuthorizerInfoRequest{
		ComponentAppID:  d.conf.Open.Wechat.AppId,
		AuthorizerAppID: authorizerAppID,
	})
	if err != nil {
		return nil, err
	}
	return &info.AuthorizerInfo, nil
}

// GetLatestWechatTemplate 获取最新的微信模版
func (d *Dao) GetLatestWechatTemplate(ctx context.Context) (template *model.WechatTemplate, err error) {
	template = new(model.WechatTemplate)
	err = d.FromContext(ctx).Order("created_at desc").First(template).Error
	return
}

// SetWechatDomains 设置请求域名
func (d *Dao) SetWechatDomains(ctx context.Context, appID string) error {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return err
	}
	_, _, err = d.wechat.WXA.ModifyDomain(ctx, token, &wechat.ModifyDomainRequest{
		Action:          "set",
		RequestDomain:   d.conf.Open.Wechat.Domains,
		WSRequestDomain: d.conf.Open.Wechat.Domains,
		UploadDomain:    d.conf.Open.Wechat.Domains,
		DownloadDomain:  d.conf.Open.Wechat.Domains,
	})
	return err
}

// SetWechatWebViewDomains 设置业务域名
func (d *Dao) SetWechatWebViewDomains(ctx context.Context, appID string) error {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return err
	}
	_, err = d.wechat.WXA.SetWebViewDomain(ctx, token, &wechat.SetWebViewDomainRequest{
		Action:        "set",
		WebViewDomain: d.conf.Open.Wechat.WebViewDomains,
	})
	return err
}

// ApplyWechatPlugins 申请微信插件
func (d *Dao) ApplyWechatPlugins(ctx context.Context, appID string) error {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return err
	}
	for _, plugin := range d.conf.Open.Wechat.Plugins {
		_, _, err := d.wechat.WXA.Plugin(ctx, token, &wechat.PluginRequest{Action: "apply", PluginAppID: plugin})
		if err != nil {
			wechatError, ok := err.(*wechat.ErrorResponse)
			if !ok || wechatError.Code != 89237 {
				return err
			}
		}
	}
	return nil
}

// Plugin 插件
type Plugin struct {
	Version  string `json:"version"`
	Provider string `json:"provider"`
}

// Ext ext_json
type Ext struct {
	ExtAppID  string                 `json:"extAppid"`
	Ext       map[string]interface{} `json:"ext"`
	Plugins   map[string]Plugin      `json:"plugins"`
	ExtEnable bool                   `json:"extEnable,omitempty"`
}

// BuildWechatCommitRequest 构造提交代码请求
func (d *Dao) BuildWechatCommitRequest(ctx context.Context, appID string, biz json.RawMessage) (*wechat.CommitRequest, error) {
	req := wechat.CommitRequest{}
	if err := json.Unmarshal(biz, &req); err != nil {
		return nil, err
	}
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	pluginResp, _, err := d.wechat.WXA.Plugin(ctx, token, &wechat.PluginRequest{Action: "list"})
	if err != nil {
		return nil, err
	}
	var canLive bool
	for _, plugin := range pluginResp.PluginList {
		if plugin.AppID == "wx2b03c6e691cd7370" && plugin.Status == 2 {
			canLive = true
			break
		}
	}
	ext := Ext{}
	if err = json.Unmarshal([]byte(req.ExtraJSON), &ext); err != nil {
		return nil, err
	}
	if ext.Plugins == nil {
		ext.Plugins = make(map[string]Plugin)
		var plugins []*model.WechatPlugin
		err = d.FromContext(ctx).Find(&plugins).Error
		if err != nil {
			return nil, err
		}
		for _, plugin := range plugins {
			ext.Plugins[plugin.Name] = Plugin{
				Version:  plugin.Version,
				Provider: plugin.Provider,
			}
		}
	}
	if !canLive {
		delete(ext.Plugins, "live-player")
	}
	extJSON, _ := json.Marshal(ext)
	req.ExtraJSON = string(extJSON)
	return &req, nil
}

// CommitWechatMini 提交微信小程序代码
func (d *Dao) CommitWechatMini(ctx context.Context, appID string, req *wechat.CommitRequest) error {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return err
	}
	if _, err = d.wechat.WXA.Commit(ctx, token, req); err != nil {
		return err
	}
	return nil
}

// CommitAuditWechatMini 提交微信小程序审核
func (d *Dao) CommitAuditWechatMini(ctx context.Context, appID string, req *wechat.SubmitAuditRequest) (string, error) {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return "", err
	}
	audit, _, err := d.wechat.WXA.SubmitAudit(ctx, token, req)
	if err != nil {
		return "", err
	}

	return audit.AuditID, nil
}

// ReleaseWechatMini 发布微信小程序
func (d *Dao) ReleaseWechatMini(ctx context.Context, appID string) error {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return err
	}
	if _, err = d.wechat.WXA.Release(ctx, token); err != nil {
		return err
	}
	return nil
}

// GetLatestWechatAuditStatus 获取最近的审核状态
func (d *Dao) GetLatestWechatAuditStatus(ctx context.Context, appID string) (*wechat.AuditStatus, error) {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	auditStatus, _, err := d.wechat.WXA.GetLatestAuditStatus(ctx, token)
	if err != nil {
		return nil, err
	}
	return auditStatus, nil
}

// GetWechatMiniTestQRCode 获取微信小程序体验码
func (d *Dao) GetWechatMiniTestQRCode(ctx context.Context, appID string) (*http.Response, error) {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	resp, err := d.wechat.WXA.GetQrCode(ctx, token, "")
	if err != nil {
		return nil, err
	}
	return resp.Response, nil
}

// CreateWechatMiniQRCode 创建小程序二维码
func (d *Dao) CreateWechatMiniQRCode(ctx context.Context, appID string, req *admin.CreateWechatMiniQRCodeRequest) (*http.Response, error) {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	wechatReq := &wechat.GetWXACodeUnlimitRequest{
		Scene:     req.Scene,
		Page:      req.Page,
		Width:     req.Width,
		AutoColor: req.AutoColor,
		IsHyaline: req.IsHyaline,
	}
	if req.LineColor != nil {
		wechatReq.LineColor = &wechat.LineColor{
			R: req.LineColor.R,
			G: req.LineColor.G,
			B: req.LineColor.B,
		}
	}
	resp, err := d.wechat.WXA.GetWXACodeUnlimit(ctx, token, wechatReq)
	if err != nil {
		return nil, err
	}
	return resp.Response, nil
}

// OauthWechatMP H5微信登录
func (d *Dao) OauthWechatMP(ctx context.Context, appID, secret string, code string) (*wechat.OauthResponse, error) {
	return d.wechat.MP.Oauth(ctx, appID, secret, code)
}

// GetWechatMPJsTicket 获取H5JsTicket
func (d *Dao) GetWechatMPJsTicket(ctx context.Context, appID string) (ticket string, err error) {
	key := fmt.Sprintf(wechatJsTicketKey, appID)
	ticket, err = d.cache.Get(ctx, key).Result()
	if err != nil {
		if err == redis.Nil {
			token, err := d.GetWechatMPAccessToken(ctx, appID)
			if err != nil {
				return "", fmt.Errorf("获取access_token失败: %d", err)
			}
			resp, err := d.wechat.MP.GetTicket(ctx, token, "jsapi")
			if err != nil {
				return "", err
			}
			// 设置ticket
			if err = d.cache.Set(ctx, key, resp.Ticket, time.Duration(resp.ExpiresIn-100)*time.Second).Err(); err != nil {
				return "", err
			}
			return resp.Ticket, nil
		}
	}
	return ticket, nil
}

// SendWechatPubMessage 发送订阅消息
func (d *Dao) SendWechatPubMessage(ctx context.Context, req *event.TaskSendWechatPubMessage) error {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, req.AppID)
	if err != nil {
		return err
	}
	data := make(map[string]interface{})
	if err := json.Unmarshal([]byte(req.Data), &data); err != nil {
		return err
	}

	// step 2: 获取或者创建template_id
	if req.AppID == "" || req.MsgType == "" {
		return nil
	}
	template, err := d.GetWechatPubTemplate(ctx, req.AppID, req.MsgType)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// case 本地未创建
			if err := d.SyncWechatPubTemplate(ctx, req.AppID); err != nil {
				return err
			}
			template, err = d.GetWechatPubTemplate(ctx, req.AppID, req.MsgType)
			if err != nil {
				return err
			}
			goto send
		}
		return err
	}

send:
	if err := d.wechat.WXA.SendPubMessage(ctx, token, &wechat.SendPubMessageRequest{
		ToUser:     req.OpenID,
		TemplateID: template.TemplateID,
		Page:       req.Page,
		Data:       data,
	}); err != nil {
		if wechatErr, ok := err.(*wechat.ErrorResponse); ok {
			switch wechatErr.Code {
			// 用户拒绝接受消息，如果用户之前曾经订阅过，则表示用户取消了订阅关系
			case 43101:
				return nil
				// 订阅模板id为空不正确, 手动再同步一次模版  case 远程未配置
			case 40037:
				// 先删除当前模版
				if err = d.DeleteWechatPubTemplate(ctx, req.AppID, req.MsgType); err != nil {
					return err
				}
				// 再同步模版
				if err = d.SyncWechatPubTemplate(ctx, req.AppID); err != nil {
					return err
				}
				template, err = d.GetWechatPubTemplate(ctx, req.AppID, req.MsgType)
				if err != nil {
					return err
				}
				goto send
			}
		}
		return err
	}
	return nil
}

// GetWechatPubTemplates 获取本地微信模版列表
func (d *Dao) GetWechatPubTemplates(ctx context.Context, domain string, appID string) ([]*model.WechatPubTemplate, error) {
	var templates []*model.WechatPubTemplate
	err := d.FromContext(ctx).Where("domain = ? AND app_id = ?", domain, appID).Find(&templates).Error
	return templates, err
}

// GetWechatPubTemplate 获取单个微信订阅消息模版
func (d *Dao) GetWechatPubTemplate(ctx context.Context, appID string, msgType string) (*model.WechatPubTemplate, error) {
	template := new(model.WechatPubTemplate)
	err := d.FromContext(ctx).Where("app_id = ? and msg_type = ?", appID, msgType).First(template).Error
	return template, err
}

// SyncWechatPubTemplate 同步微信订阅消息模版
func (d *Dao) SyncWechatPubTemplate(ctx context.Context, appID string) error {
	var templates []*model.WechatPubTemplate
	err := d.FromContext(ctx).Where("app_id = ?", appID).Find(&templates).Error
	if err != nil {
		return err
	}

	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return err
	}

	resp, err := d.wechat.WXA.GetCategory(ctx, token)
	if err != nil {
		return err
	}
	categories := make([][2]int64, len(resp.Categories))
	for i, c := range resp.Categories {
		categories[i] = [2]int64{c.First, c.Second}
	}
	var addTypes []string
	var addCategories [][2]int64
	allTemplates := types.AllPubTemplates()
	existSet := make(map[string]struct{}, len(templates))
	existCategories := make(map[[2]int64]struct{}, len(templates))

	for _, c := range categories {
		existCategories[c] = struct{}{}
	}
	for _, t := range templates {
		existSet[t.MsgType] = struct{}{}
	}
	for _, t := range allTemplates {
		if _, ok := existSet[t.Type]; !ok {
			addTypes = append(addTypes, t.Type)
			if _, ok := existCategories[t.Category]; !ok {
				addCategories = append(addCategories, t.Category)
			}
		}
	}
	// 先检查类目信息
	if len(addCategories) > 0 {
		for _, category := range categories {
			err := d.wechat.WXA.AddCategory(ctx, token, &wechat.AddCategoryRequest{Categories: []wechat.AddCategoryItem{
				{
					First:  category[0],
					Second: category[1],
				},
			},
			},
			)
			if err != nil {
				return err
			}
		}
	}
	if len(addTypes) > 0 {
		for _, msgType := range addTypes {
			template := types.GetPubTemplate(msgType)
			if template.TitleID != "" {
				resp, err := d.wechat.WXA.AddPubTemplate(ctx, token, &wechat.AddPubTemplateRequest{
					TitleID:   template.TitleID,
					KeyIDs:    template.KeyIDS,
					SceneDesc: template.Desc,
				})
				if err != nil {
					return err
				}
				// 保存到本地模版
				if err := d.FromContext(ctx).Create(&model.WechatPubTemplate{
					TemplateID: resp.PriTmplID,
					AppID:      appID,
					MsgType:    msgType,
					Domain:     template.Domain,
				}).Error; err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// DeleteWechatPubTemplate 删除无效的模版
func (d *Dao) DeleteWechatPubTemplate(ctx context.Context, appID string, msgType string) error {
	return d.FromContext(ctx).
		Where("app_id = ? AND msg_type = ?", appID, msgType).
		Delete(&model.WechatPubTemplate{}).Error
}

// GetWechatLivingRoomID 获取正在直播的房间号
func (d *Dao) GetWechatLivingRoomID(ctx context.Context, appID string) (int64, error) {
	token, err := d.GetWechatAuthorizerAccessToken(ctx, appID)
	if err != nil {
		return 0, err
	}
	resp, _, err := d.wechat.WXA.GetLiveInfo(ctx, token, &wechat.GetLiveInfoRequest{
		Start: 0,
		Limit: 100,
	})
	if err != nil {
		return 0, err
	}
	for _, r := range resp.RoomInfo {
		if r.LiveStatus == 101 {
			return r.RoomID, nil
		}
	}
	return 0, nil
}

// FindWechatMiniAppByBrandIDs 查询apps根据brand_ids
func (d *Dao) FindWechatMiniAppByBrandIDs(ctx context.Context, brandIds []int64) (apps []*model.BrandMiniApp, err error) {
	err = d.FromContext(ctx).Where("brand_id in ? and platform = ?", brandIds, types.Wechat).Find(&apps).Error
	return
}
